--[[
	By Seleven @GoVion,20181213
	for MITSUBISHI,L02CPU,PLC
--]]


--头部标志
modBusReadHeader = "500000FFFF03000C001000"
modBusWriteHeader = "500000FFFF030010001000"

modBusWriteHeaderPrefix="500000FFFF0300"
modBusWriteHeaderCpu="1000";
fixHeaderBytes=12;--固定头部信息长度，从CPU定时器到寄存器数量之间的域

--读字位标志
modBusReadBit = "01040100"
modBusReadByte = "01040000"

--写字位标志
modBusWriteBit = "01140100"
modBusWriteByte = "01140000"

--字位分隔符
modBusSepBit = "0090"
modBusSepByte = "00A8"


--PLC回复头部信息
modBusBackHeader = "D00000FFFF0300"

--PLC正常写回复--写值的正确回复,固定的格式
modBusWriteBack = "D00000FFFF030002000000"


--将字符串按格式转为16进制串
function str2hex(hex)
	--判断输入类型
	if (type(hex)~="string") then
		return nil,"hex2str invalid input type"
	end
	--拼接字符串
	local index=1
	local ret=""
	for index=1,hex:len() do
		ret=ret..string.format("%02X",hex:sub(index):byte())
	end
 
	return ret
end

--将16进制串转换为字符串
function hex2str(str)
	--判断输入类型	
	if (type(str)~="string") then
	    return nil,"str2hex invalid input type"
	end
	--滤掉分隔符
	str=str:gsub("[%s%p]",""):upper()
	--检查内容是否合法
	if(str:find("[^0-9A-Fa-f]")~=nil) then
	    return nil,"str2hex invalid input content"
	end
	--检查字符串长度
	if(str:len()%2~=0) then
	    return nil,"str2hex invalid input lenth"
	end
	--拼接字符串
	local index=1
	local ret=""
	for index=1,str:len(),2 do
	    ret=ret..string.char(tonumber(str:sub(index,index+1),16))
	end
	return ret
end

--10进制转hex码
function bin2hex(addr)
	local hight = tonumber(addr)/256
	local low = tonumber(addr)%256
	local hByte = string.format("%02x",math.floor(hight))
	local lByte = string.format("%02x",math.floor(low))
    return lByte .. hByte
end

--10进制转hex码,单个字符
local function num2hexByte(addr)
	local low = tonumber(addr)%256
	local lByte = string.format("%02x",low)
    return lByte
end
--去除字符串空格
function trim(s)
	return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end 
 
--读取位指令,addr 寄存器地址,num 寄存器个数
function commondReadBit(addr,num)
	local ret = ""
	local addrHex = bin2hex(addr);
	local numHex = bin2hex(num);
	ret = modBusReadHeader .. modBusReadBit .. addrHex .. modBusSepBit .. numHex;
	return ret
end

--读取位指令返回截取,recv 为接收到的字符
function commondReadBitBack(recv)
	local ret = ""
	local recvData = string.gsub(recv,modBusBackHeader,"");--去除头部
	local numL = string.sub(recvData,1,2);--获取数据长度,低位
	if(numL == nil)then return ret end
	
	local numH = string.sub(recvData,3,4);--获取数据长度,高位
	if(numH == nil)then return ret end
	
	local num = tonumber(numL,16)+tonumber(numH,16)*256
	
	if(num<0)then return ret end
	local data = string.sub(recvData,9,8+(num*2));
	if(data ~= nil)then ret = data end
	return ret
end

--写入位指令,addr 寄存器地址,num 寄存器个数,data 写入内容,写入内容为16进制,eg:11为0x11
function commondWriteBit(addr,num,...)
	local ret = ""
	local addrHex = bin2hex(addr);
	local numHex = bin2hex(num);
	local args = {...}
	local data = "";
	for i=1,tonumber(num)/2 do
		if(args[i] ~= nil)then
			data = data .. bin2hex(args[i])
		else
			data = data .. "00"
		end
	end
	
	ret = modBusWriteHeader .. modBusWriteBit .. addrHex .. modBusSepBit .. numHex .. data;
	return ret
end


--读取字指令,addr 寄存器地址,num 寄存器个数
function commondReadByte(addr,num)
	local ret = ""
	local addrHex = bin2hex(addr);
	local numHex = bin2hex(num);
	ret = modBusReadHeader .. modBusReadByte .. addrHex .. modBusSepByte .. numHex;
	return ret
end

--读取字指令返回截取,recv 为接收到的字符
function commondReadByteBack(recv)
	local ret = ""
	local recvData = string.gsub(recv,modBusBackHeader,"");--去除头部
	local numL = string.sub(recvData,1,2);--获取数据长度,低位
	if(numL == nil)then return ret end
	
	local numH = string.sub(recvData,3,4);--获取数据长度,高位
	if(numH == nil)then return ret end
	
	local num = tonumber(numL,16)+tonumber(numH,16)*256--求出最终的数据长度
--	print("commondReadByteBack num==>>",num)
	if(num<0)then return ret end
	local data = "";
--	print("commondReadByteBack==>>src data==>",recv)
--	print("commondReadByteBack==>>remove head=",recvData);
	for i = 9,(num*2),2 do
		local byteData = string.sub(recvData,i,i+1);
			--print("tonumber(byteData) ==",tonumber(byteData));
			--print(byteData);
		if(byteData ~= nil and tonumber(byteData,16) ~= nil)then
			--去掉不可见字符.
			if(tonumber(byteData,16) >0x20)then
				data = data .. byteData;
			end
		end
	end
	ret = data;
--	print("commondReadByteBack==>>result data=",ret);
	return ret
end

--写入字指令,addr 寄存器地址,num 寄存器个数,.... 写入内容,写入内容为10进制
function commondWriteByte(addr,num,...)
	local ret = ""
	local addrHex = bin2hex(addr);
	local numHex = bin2hex(num);
	local args = {...}
	local data = "";
	for i=1,tonumber(num) do
		if(args[i] ~= nil)then
			data = data .. bin2hex(tonumber(args[i]))
		else
			data = data .. "0000"
		end
	end
	
	local dataBytes=tonumber(num)*2+fixHeaderBytes; --数据长度
	ret = modBusWriteHeaderPrefix .. bin2hex(dataBytes) .. modBusWriteHeaderCpu .. modBusWriteByte .. addrHex .. modBusSepByte .. numHex .. data;
--	ret = modBusWriteHeader .. modBusWriteByte .. addrHex .. modBusSepByte .. numHex .. data;
	return ret
end


--写入字指令,addr 寄存器地址,num 寄存器个数dataString 写入内容,写入内容为字符串,低位为hex码,高位为00
function commondWriteByteString(addr,dataString)
	local ret = ""
	local addrHex = bin2hex(addr);
	if(string.len(dataString) == 0)then
		return ret
	end

	local numHex = bin2hex(string.len(dataString)/2);
	local data = "";
	for i=1,tonumber(string.len(dataString)),2 do
		local byteC = string.sub(dataString,i,i);
		local byteD=string.sub(dataString,i+1,i+1)
		
		
		if byteD ~= nil then 
			data = data .. str2hex(byteD)
		else
			data = data .. "00"
		end
				
		if byteC ~= nil then 
			data = data .. str2hex(byteC)
		else
			data = data .. "00"
		end
		
		print("data=",data)
	end
	local dataBytes=string.len(dataString)+fixHeaderBytes; --数据长度
	ret = modBusWriteHeaderPrefix .. bin2hex(dataBytes) .. modBusWriteHeaderCpu .. modBusWriteByte .. addrHex .. modBusSepByte .. numHex .. data;
--	ret = modBusWriteHeader .. modBusWriteByte .. addrHex .. modBusSepByte .. numHex .. data;
	return ret
end

--读取的回复,转换为明文字符串
function commondReadByteBackString(recv)
	local ret = ""
	local data = commondReadByteBack(recv)
	if(data ~= "")then
		ret = hex2str(data);
	end
	return ret
end


--[[

	至此,ModBus所需的一般字符处理已经基本完善,以下为封装了Socket通信的ModBus
	方法

--]]

--定义ModeBusClient类  
  
--定义一个方法，函数闭包实现一个类的概念  
--[[
	name = 对象名称,hostip = PLC的ip地址,hostport = 通信端口
	注意:时间单位ms
--]]
PLCModBusSocket = nil;--该对象假如不引出为全局对象,析构的时候会出错.
function ModBusClient(name,plcSettings)  
    local self = {}  
--	print("plcSettings==",plcSettings)
	local addressTable=split(plcSettings,":");
	local ip=addressTable[1];
	local port=tonumber(addressTable[2]);

	print("=======>>ip==",ip,port)
	self.host = ip --PLC的IP地址
	self.port = port --端口
	self.HEARTBEAT={};
	self.QRCODEREG={};

	PLCModBusSocket = AresTcpClient:govionGetObj(name);	--创建socket对象
	PLCModBusSocket:setByteMode(true);
	PLCModBusSocket:setHasLenInfo(false);
	PLCModBusSocket:setPFrameWorkShare(pFrameWork:getFrameWorkShare()); --架构所需
	
	self.HeartBeatStartTime = os.clock();--心跳包起始计时时间
	
	self.HEARTBEAT["Addr"] = tonumber(addressTable[3]);	--心跳包寄存器
	self.HEARTBEAT["Num"]= string.format(math.ceil(addressTable[4]),"%d");
	self.QRCODEREG["Addr"] = addressTable[5];		--二维码寄存器
	self.QRCODEREG["Num"]= string.format(math.ceil(addressTable[6]),"%d");
	self.WARNING = 10162;	--报警寄存器
	
	self.SENDERROR = "sendFail";
	self.BROKE = "broke";
	self.TIMEOUT = "timeOut";
	self.MSGOUT = "msgOut";

--	print("HEARTBEAT addr=",self.HEARTBEAT["Addr"])
--	print("HEARTBEAT Num=",self.HEARTBEAT["Num"])
	--变量定义
	self.QR_CODE=""	--获取的二维码值

	--初始化socket设置
	self.initSocket = function(hostAddr,portNum)
		self.host = hostAddr;
		self.port = tonumber(portNum);
	end

	self.delete = function()
		PLCModBusSocket:close();
		PLCModBusSocket:delete();
	end
	
	--打开连接,item 当前执行项,timeWait 连接时间
	self.openConection = function(item,timeWait)
		if(PLCModBusSocket:isConnected() == true)then
			return true
		end
		--注意:连接的超时时间是代表在返回连接失败之前,可以再次发起连接的最长时间.
		PLCModBusSocket:open(self.host,self.port,55000);
		local stTime = os.clock()
		while(false == pFrameWork.workOut)
		do
			if(self.isConnected() == true)then
				return true
			end
			local endTIme = os.clock()
			--/1000是因为os.clock的单位是s.传入时间统一为ms
			if((endTIme - stTime)>timeWait/1000)then
				return false
			end
			item:delayMs(100);
		end
		return false
	end

	--判断是否连接的状态
	self.isConnected = function()
		if(PLCModBusSocket:isConnected() == true)then
			return true
		end
		return false
	end
	
	--打开并发送
	self.openSend = function(item,sendMsg,timeWait)
		if(false == self.isConnected())then
			if(false == self.openConection(item,timeWait))then
				print("open-----FAIL")
				return false
			end
		end
--		print("openSend write msg==>>",sendMsg)
		local ret = PLCModBusSocket:write(sendMsg);
--		print("write bytes=>",ret);
		if(ret == nil or ret < 0)then return false end
		return true;
	end
	
	--打开并接收
	self.openRecv = function(item,timeWait)
		if(false == self.isConnected())then
			if(false == self.openConection(item,timeWait))then
				return self.BROKE
			end
		end
		local recv = ""
		recv = PLCModBusSocket:read(timeWait);
--		print("openRecv==>>",recv);
		if(recv ~= nil and recv ~= "" and recv ~= "nil")then
			return recv;
		end
		return recv;
	end
	
	--发送modbus写位消息,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.setBitBus = function(item,timeWait,addr,num,...)
		local msg = ""
		msg = commondWriteBit(addr,num,...)
--		print("setBitBus********=",msg);
		if(msg == "")then
			return self.MSGOUT
		end
		if (false == self.openSend(item,msg,timeWait)) then 			
			return self.SENDERROR 
		end
		local recv = self.openRecv(item,timeWait)
		return recv;
	end
	
	
	--发送modbus写位消息item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.setBitBusEx = function(item,timeWait,addr,num,...)
		local startTime = os.clock();
		local timeOut = false;
		while(false == timeOut and false== pFrameWork.workOut)
		do
			local ret = self.setBitBus(item,timeWait,addr,num,...);
			if(ret == self.SENDERROR)then
				return false;
			elseif(ret == self.BROKE)then
				return false;
			elseif(ret == self.MSGOUT)then
				return false;
			elseif(ret == self.TIMEOUT)then --假如超时,不需要继续进行	
				--return false;
			elseif(ret == modBusWriteBack)then
				return true;
			end
			local endTime = os.clock();
			this:delayMs(200);
			if((endTime - startTime)>timeWait/1000)then
				timeOut = true;
			end	
		end
		return false;
	end
	
	--发送modbus写字消息,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.setByteBus = function(item,timeWait,addr,num,...)
		local msg = ""
		msg = commondWriteByte(addr,num,...)
--		print("setByteBus send==>>",msg)
		if(msg == "")then
			return self.MSGOUT
		end
		if(false == self.openSend(item,msg,timeWait)) then
			return self.SENDERROR 
		end
		local recv = self.openRecv(item,timeWait)
--		print("setByteBus recv==>>",recv)
		return recv;
	end
	
	--发送modbus写字消息,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.setByteBusEx = function(item,timeWait,addr,num,...)
		local startTime = os.clock();
		local timeOut = false;
		while(false == timeOut and false== pFrameWork.workOut)
		do
			local ret = self.setByteBus(item,timeWait,addr,num,...);
--			print("setByteBusEx==recv>>",ret)
			if(ret == self.SENDERROR)then
				return false;
			elseif(ret == self.BROKE)then
				return false;
			elseif(ret == self.MSGOUT)then
				return false;
			elseif(ret == modBusWriteBack)then
				return true;
			end
			local endTime = os.clock();
			if((endTime - startTime)>timeWait/1000)then
				timeOut = true;
			end	
		end
		return false;
	end
	
	--发送modbus写字消息,直接填字符串,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.setByteStringBus = function(item,timeWait,addr,dataString)
		local msg = ""
		msg = commondWriteByteString(addr,dataString)
		if(msg == "")then
			return self.MSGOUT
		end
		if(false == self.openSend(item,msg,timeWait)) then return self.SENDERROR end
		local recv = self.openRecv(item,timeWait)
		return recv
	end
	
	--发送modbus写字消息,直接填字符串,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.sendByteStringBusEx = function(item,timeWait,addr,dataString)
		local startTime = os.clock();
		local timeOut = false;
		while(false == timeOut and false== pFrameWork.workOut)
		do
	
			local ret = self.setByteStringBus(item,timeWait,addr,dataString);
			--print("sendByteStringBusEx==>>",ret);
			if(ret == self.SENDERROR)then
				return false;
			elseif(ret == self.BROKE)then
				return false;
			elseif(ret == self.MSGOUT)then
				return false;
			elseif(ret == self.TIMEOUT)then --假如超时,不需要继续进行	
				--return false;
			elseif(ret == modBusWriteBack)then
				return true;
			end
			local endTime = os.clock();
			if((endTime - startTime)>timeWait/1000)then
				timeOut = true;
			end	
		end
		return false;
	end
	
	--modbus读位消息,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.getBitBus = function(item,timeWait,addr,num)
		local msg = ""
		msg = commondReadBit(addr,num)
		if(msg == "")then
			return self.MSGOUT
		end
		if(false == self.openSend(item,msg,timeWait)) then return self.SENDERROR end
		local recv = self.openRecv(item,timeWait)
		if(recv == self.BROKE or recv == self.TIMEOUT)then
			return recv
		end
		if(recv == "")then
			return self.TIMEOUT;
		end
		return commondReadBitBack(recv)
	end
	
	--modbus读位消息,recvMatch需要判定的正确值,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.getBitBusEx = function(recvMatch,item,timeWait,addr,num)
		local startTime = os.clock();
		local timeOut = false;
		while(false == timeOut and false== pFrameWork.workOut)
		do
			local ret = self.getBitBus(item,timeWait,addr,num);
			--print("getBitBusEx==>>",ret);
			--print("recvMatch==>>",recvMatch);
			if(ret == self.SENDERROR)then
				return false;
			elseif(ret == self.BROKE)then
				return false;
			elseif(ret == self.MSGOUT)then
				return false;
			elseif(ret == self.TIMEOUT)then --假如超时,不需要继续进行	
				--return false;
			elseif(ret == recvMatch)then
				return true;
			end
			this:delayMs(200);
			local endTime = os.clock();
			if((endTime - startTime)>timeWait/1000)then
				timeOut = true;
			end	
		end
		return false;
	end
	
	--modbus读字消息,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.getByteBus = function(item,timeWait,addr,num)
		local msg = ""
		msg = commondReadByte(addr,num)
		if(msg == "")then
			return self.MSGOUT
		end
		if(false == self.openSend(item,msg,timeWait)) then return self.SENDERROR end
		local recv = self.openRecv(item,timeWait)
		if(recv == self.BROKE or recv == self.TIMEOUT)then
			return recv
		end
		if(recv == "")then
			return self.TIMEOUT;
		end
		return commondReadByteBack(recv)
	end
	
	--modbus读字消息,recvMatch需要判定的正确值,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.getByteBusEx = function(recvMatch,item,timeWait,addr,num)
		local startTime = os.clock();
		local timeOut = false;
		while(false == timeOut and false== pFrameWork.workOut)
		do
			local ret = self.getByteBus(item,timeWait,addr,num);
			--print("getByteBusEx==>>",ret);
			--print("recvMatch==>>",recvMatch);
			if(ret == self.SENDERROR)then
				return false;
			elseif(ret == self.BROKE)then
				return false;
			elseif(ret == self.MSGOUT)then
				return false;
			elseif(ret == self.TIMEOUT)then --假如超时,不需要继续进行	
				--return false;
			elseif(ret == recvMatch)then
				return true;
			end
			local endTime = os.clock();
			if((endTime - startTime)>timeWait/1000)then
				timeOut = true;
			end	
		end
		return false;
	end

	--modbus读字消息,自动转成字符串,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.getByteStringBus = function(item,timeWait,addr,num)
		local msg = ""
		msg = commondReadByte(addr,num)
--		print("getByteStringBus==send==>>",msg)
		if(msg == "")then
			return self.MSGOUT
		end
		if(false == self.openSend(item,msg,timeWait)) then return self.SENDERROR end
		local recv = self.openRecv(item,timeWait) --"形如 "65" 
--		print("getByteStringBus==recv==>>",recv)
		if(recv == self.BROKE or recv == self.TIMEOUT)then
			return recv
		end
		if(recv == "")then
			return "";
		end

		return commondReadByteBackString(recv) --形如 "A"
	end
	
	--等待读取非空值
	self.blockReadNonEmptyStr = function(item,timeWait,addr,num)
		local startTime = os.clock();
		local timeOut = false;
		while(false == timeOut and false== pFrameWork.workOut)
		do
			local ret = self.getByteStringBus(item,timeWait,addr,num);
--			print("getByteStringBusEx==>>",ret);
			if(ret == self.SENDERROR)then
				return false,"";
			elseif(ret == self.BROKE)then
				return false,"";
			elseif(ret == self.MSGOUT)then
				return false,"";
			elseif(ret ~= "")then
				return true,ret;
			end
			local endTime = os.clock();
			if((endTime - startTime)>timeWait/1000)then
				timeOut = true;
			end	
		end
		return false,"";	
	end

	--modbus读字消息,recvMatch需要判定的正确值,自动转成字符串,item 当前项,timeWait 超时时间,addr 地址,num 寄存器数量,... 数据
	self.getByteStringBusEx = function(recvMatch,item,timeWait,addr,num)
		local startTime = os.clock();
		local timeOut = false;
		while(false == timeOut and false== pFrameWork.workOut)
		do
			local ret = self.getByteStringBus(item,timeWait,addr,num);
			--print("getByteStringBusEx==>>",ret);
			--print("recvMatch==>>",recvMatch);
			if(ret == self.SENDERROR)then
				return false;
			elseif(ret == self.BROKE)then
				return false;
			elseif(ret == self.MSGOUT)then
				return false;
			elseif(ret == self.TIMEOUT)then --假如超时,不需要继续进行	
				--return false;
			elseif(ret == recvMatch)then
				return true;
			end
			local endTime = os.clock();
			if((endTime - startTime)>timeWait/1000)then
				timeOut = true;
			end	
		end
		return false;
	end
	
    return self  
end  
